/**
 * class pineapple.VirtMachine
 *
 * March 28, 2012 
 * @authors Roman Kotusev, Darjuš Ilcevič, Kristis Vaškys
 */

package pineapple;

import java.io.BufferedReader;
import java.io.FileReader;

public class VirtMachine {
    
    String buffer = "";
    int i = 0;
    int codeStart = 0;                      // start of code segment
    String filename;
    String [] line  = new String[256];
    Processor processor;
    boolean checked = false;                // if file is checked for errors
    boolean loaded = false;                 // if code is loaded
    boolean errorFlag = false;
    int length = 0;                         // length of file (in lines)
    RealMachineModel GUI;
    boolean stepByStep = true;
    
    /**
     * Creates a Virtual Machine based on given processor and Interface
     * @param proc processor Machine is working on
     * @param GUI interface Machine is working on
     */
    VirtMachine(Processor proc, RealMachineModel GUI){
         processor = proc;
         this.GUI = GUI;
    }
    
    /**
     * Checks given file if it contains correct code for "pineapple"
     * 
     * @param name the name of a file to check
     */
    public void checkData(String name){
        errorFlag = false;
        filename = name;
        GUI.setFilenameLabel(name);
        
        /*
         * Reading from file
         */
        
        try {
            BufferedReader in = new BufferedReader(new FileReader(filename));
            while ((buffer = in.readLine()) != null){
                line[i] = buffer.trim();
                line[i] = buffer.replaceAll(" " , "");
                i++;
            }
            length = i;      
            
            /*
             *  Parse the code
             */
            
            i = 0;
            if (!line[i].equals("DATA")) {
                error("Wrong Start of file (DATA expected)");
            } else {
                i++;
                while(!line[i].equals("CODE") && !errorFlag) {
                    dataCheck(line[i]);
                    i++;
                    if (i == 66) {
                        error("Data overflow, keep within 64 lines");
                    }
                }
                i++;
                codeStart = i;
                while(!line[i].equals("STOP") && !errorFlag){
                    codeCheck(line[i]);
                    i++;
                    if (i - codeStart == 64) {
                        error("Code overflow, keep within 64 lines");
                    }
                }
            }
            checked = !errorFlag;
        }
        catch (NullPointerException e) {
            error("Unexpected end of file");
        }
        catch (Exception e) {
            error(e.toString());
        }
     }
    
    /**
     * Loads the file contained in 'filename' into Code and Data segments.
     * The file has to be declared and checked with checkData(String name).
     */
    public void loadData(){
        errorFlag = false;
        
        if (!checked) {
            error("Data Loader: File not parsed");
        }  else {
            /*
             * Loading Data segment
             */
            for (i = 0; i < codeStart && !errorFlag; i++) {
                parseData(line[i]);
            }
            /*
             * Loading Code segment
             */
            for (i = codeStart; i < length && !errorFlag; i++) {
                processor.setCodeCommand(i - codeStart, line[i]);
            }
            GUI.updateGUI(processor);
        }
        
        loaded = !errorFlag;
    }

    /**
     * Executes the code contained in Code segment.
     * The file has to be declared, checked and loaded with 
     * checkData(String name) and loadData()
     */
    public void executeCode() {
        errorFlag = false;
        
        if (!loaded) {
            error("Code Execution: File not loaded");
            GUI.enableCloseButton();
        } else {
            GUI.enableExecuteAllButton();
            GUI.enableExecuteStepButton();
            GUI.enableCloseButton();
            String current_command = processor.getNextCommand();
            while ((processor.getIF() == 0) && !errorFlag){
                parseCode(current_command);
                if (GUI.executeAllButtonClick == false){
                    while(GUI.stepButtonClick == false 
                            && GUI.closeButtonClick == false){}
                    if (GUI.closeButtonClick == true) return;
                }
                if (GUI.closeButtonClick == true) return;
                GUI.updateGUI(processor);
                current_command = processor.getNextCommand();
                GUI.stepButtonClick = false;
            }
            GUI.disableExecuteAllButton();
            GUI.disableExecuteStepButton();
        }      
    }
    
    /**
     * Resets processor's working registers, memory and GUI, returning Virtual
     * machine to initial state.
     */
    public void closeMachine() {        
        while (GUI.closeButtonClick == false){}
        processor.resetRegistres();
        processor.resetMemory();
        GUI.resetGUI();
        buffer = "";
        i = 0;
        codeStart = 0;
        filename = "";
        line = new String[256];
        checked = false;
        errorFlag = false;
        length = 0;
        stepByStep = true;
    }

    private void dataCheck(String msg){
        if (msg.substring(0,2).equals("LW")){
            Integer.parseInt(msg.substring(2, 4));
            if (msg.length() > 8) error("Too long param for LW");
        } else if (msg.substring(0,2).equals("LN")){
            Integer.parseInt(msg.substring(2, 4));
            Integer.parseInt(msg.substring(4));
        } else error ("Wrong data input (expected LN, LW)");
    }

    private void codeCheck(String msg){
        if (msg.length() > 4) {
            error("Code command is too long: "+msg);
        }
        if (msg.length() < 2) {
            error("Code command is too short: "+msg);
        }
        if (msg.equals("ADD")
            || msg.equals("SUB")
            || msg.equals("MUL")
            || msg.equals("DIV")
            || msg.equals("MOD")
            || msg.equals("CMP")
            || msg.equals("STOP")
            || msg.equals("SWCH")
            || msg.equals("PUSH")
            || msg.equals("POP")) {
                
        } else if (msg.substring(0,2).equals("JM")
            || msg.substring(0,2).equals("JE")
            || msg.substring(0,2).equals("JN")
            || msg.substring(0,2).equals("JA")
            || msg.substring(0,2).equals("JB")
            || msg.substring(0,2).equals("L1")
            || msg.substring(0,2).equals("L2")
            || msg.substring(0,2).equals("R1")
            || msg.substring(0,2).equals("R2")
            || msg.substring(0,2).equals("DJ")) {
                Integer.parseInt(msg.substring(2, 4));
        } else if (msg.substring(0, 3).equals("INT")) {
            if (msg.length() < 4) {
                error("No param for INT");
            }
        } else if (msg.substring(0,2).equals("IN") 
                || msg.substring(0,2).equals("DE")) {
            if (msg.length()<4) {
                error("No param for "+msg.substring(0, 2));
            }
            if (!msg.substring(2,4).equals("R1") 
                && !msg.substring(2,4).equals("R2")
                && !msg.substring(2,4).equals("DS")
                && !msg.substring(2,4).equals("CS")
                && !msg.substring(2,4).equals("SS")){
                    error("Wrong param for "+msg.substring(0, 2)
                            + ", (R1,R2,DS,CS,SS expected)");
            }
        } else error("Unknown command " + msg);
    }

    private void parseData(String msg){
        if (msg.substring(0, 2).equals("LW")) {
            processor.LW(msg.substring(4), 
                    Integer.parseInt(msg.substring(2, 4)));       
        } else if (msg.substring(0, 2).equals("LN")){
            processor.LN(Integer.parseInt(msg.substring(4)), 
                    Integer.parseInt(msg.substring(2, 4)));
        }      
    }

    private void parseCode(String msg){
        msg = msg.trim();
        if (msg.equals("ADD")) processor.ADD();
        else if (msg.equals("SUB")) processor.SUB();
        else if (msg.equals("MUL")) processor.MUL();
        else if (msg.equals("DIV")) processor.DIV();
        else if (msg.equals("MOD")) processor.MOD();
        else if (msg.equals("CMP")) processor.CMP();
        else if (msg.equals("SWCH")) processor.SWCH();
        else if (msg.equals("PUSH")) processor.PUSH();
        else if (msg.equals("POP")) processor.POP();
        else if (msg.equals("STOP")) processor.STOP();
                
        else if (msg.substring(0, 2).equals("JM")) processor.JM(Integer.parseInt(msg.substring(2, 4)));
        else if (msg.substring(0, 2).equals("JE")) processor.JE(Integer.parseInt(msg.substring(2, 4)));
        else if (msg.substring(0, 2).equals("JN")) processor.JN(Integer.parseInt(msg.substring(2, 4)));
        else if (msg.substring(0, 2).equals("JA")) processor.JA(Integer.parseInt(msg.substring(2, 4)));
        else if (msg.substring(0, 2).equals("JB")) processor.JB(Integer.parseInt(msg.substring(2, 4))); 
        else if (msg.substring(0, 2).equals("L1")) processor.L1(Integer.parseInt(msg.substring(2, 4)));
        else if (msg.substring(0, 2).equals("L2")) processor.L2(Integer.parseInt(msg.substring(2, 4)));
        else if (msg.substring(0, 2).equals("R1")) processor.R1(Integer.parseInt(msg.substring(2, 4)));
        else if (msg.substring(0, 2).equals("R2")) processor.R2(Integer.parseInt(msg.substring(2, 4)));
        else if (msg.substring(0, 2).equals("DJ")) processor.DJ(Integer.parseInt(msg.substring(2, 4)));

        else if (msg.substring(0, 3).equals("INT")){
            int val = 0;
            if (msg.substring(3, 4).equals("1")) val = 1;
            else if (msg.substring(3, 4).equals("2")) val = 2;
            else if (msg.substring(3, 4).equals("B")) val = 11;
            else if (msg.substring(3, 4).equals("C")) val = 12;
            else if (msg.substring(3, 4).equals("D")) val = 13;
            else if (msg.substring(3, 4).equals("E")) val = 14;
            else if (msg.substring(3, 4).equals("F")) val = 15;
            processor.INT(val);
        } else if (msg.substring(0, 2).equals("IN")) {
            processor.IN(msg.substring(2, 4));
        } else if (msg.substring(0, 2).equals("DE")) {
            processor.DE(msg.substring(2, 4));
        } else error("(exec) Unknown command " + msg);
    }

    private void error(String mes){
        GUI.showAlertDialog(mes);
        errorFlag = true;
    }
    
    private void error(){
        GUI.showAlertDialog("Uknown error.");
        errorFlag = true;
    }
    
    private void msg(String mes){
        GUI.rAddConsoleField(mes);
    }
    
}